<?php
require_once 'Utils.php';

/**
 * Class OAuthStoreMySql
 * Edit from original library OAuth-PHP of Marc Worrell <marcw@pobox.com> 
 *
 * Description for class OAuthStoreMySql
 * 
 * @editor: Phong Linh <langxangvn@gmail.com>
 * @see http://code.google.com/p/oauth-php
*/
class OAuthStoreMySql {
	
	/**
	 * Max request token TTL
	 */
	protected $_maxRequestTokenTtl = 3600;
	
	/**
	* Timestamp expired time
	*/
	protected $_timestampThreshold = 36000;
	
	/**
	* Max valid time if not specific expired time for access token
	*/	
	protected $_maxTokenExpiredDate = '2020-12-31';
	
	/**
	 * Find stored credentials for the consumer key and token. Used by an OAuth server 
	 * when verifying an OAuth request.
	 *
	 * @param string $consumerKey Consumer/API key
	 * @param string $token Token value
	 * @param string $tokenType 'access' or 'request' or not specific
	 * @return array Information user to verify request
	 *
	 */
	public function getSecretsForVerify($consumerKey, $token, $tokenType = 'access') {
		$db = Zend_Db_Table::getDefaultAdapter();
		$select = new Zend_Db_Select($db);	
		
		if($tokenType === false) { // for check when process request token		
			$select->from('registries', array('id', 'consumer_key', 'consumer_secret'))
				->where('consumer_key = ?', $consumerKey)
				->where('enabled = 1');
			
			// Dump SQL query for test purpose	
			//var_dump($select->__toString());			
			
			$rs = $db->fetchRow($select);	
			if($rs) {
				$rs['registry_id'] = $rs['id'];
				
				// data from tokens table is not exists
				$rs['token_id'] = false;
				$rs['token'] = false;				
				$rs['token_secret'] = false;				
				$rs['user_id'] = false;
				$rs['authorized'] = false;				
			}				
		} else {	// if have a token already		
			$select->from(array('A' => 'registries'), 
				array('A.consumer_key', 'A.consumer_secret', 
					'A.app_title', 'A.app_desc', 'A.app_uri'))
				->join(array('B' => 'tokens'), 'A.id = B.registry_id', 
					array('B.user_id', 'B.authorized', 'B.registry_id', 
						'token_id' => 'B.id', 'B.token', 'B.token_secret'))
				->where('A.consumer_key = ?', $consumerKey)
				->where('B.token = ?', $token)
				->where('B.token_type = ?', $tokenType)
				->where('A.enabled = 1')
				->where('B.token_ttl >= NOW()');
			
			// Dump SQL query for test purpose	
			//var_dump($select->__toString());			
			
			$rs = $db->fetchRow($select);
		}
		
		if (empty($rs)) {
			throw new Exception('The consumer_key "' . $consumerKey . '" token "' . 
				$token . '" combination does not exist or is not enabled.');
		}
		
		return $rs;
	}
	
	/**
	 * This is method updateConsumer
	 *
	 * @param mixed $consumer This is a description
	 * @param mixed $userId This is a description
	 * @param mixed $isAdmin This is a description
	 * @return mixed This is the return value description
	 *
	 */	
	public function updateConsumer($consumer, $userId, $isAdmin = false) {
		
	}
		
	/**
	 * This is method deleteConsumer
	 *
	 * @param mixed $consumerKey This is a description
	 * @param mixed $userId This is a description
	 * @param mixed $isAdmin This is a description
	 * @return mixed This is the return value description
	 *
	 */	
	public function deleteConsumer($consumerKey, $userId, $isAdmin = false) {
		
	}
	
	/**
	 * This is method getConsumer
	 *
	 * @param mixed $consumerKey This is a description
	 * @param mixed $userId This is a description
	 * @param mixed $isAdmin This is a description
	 * @return mixed This is the return value description
	 *
	 */	
	public function getConsumer($consumerKey, $userId, $isAdmin = false) {
		
	}
	
	/**
	* Fetch the consumer request token, by request token.
	* 
	* @param string token
	* @return array  token and consumer details
	*/
	public function getRequestToken($token, $enabled = 1) {
		$db = Zend_Db_Table::getDefaultAdapter();		// data adapter
		
		$select = new Zend_Db_Select($db);		
		$select->from(array('A' => 'tokens'), array('token_id' => 'A.id', 'A.token', 
			'A.user_id', 'A.authorized',
			'A.token_secret', 'A.token_type', 'A.callback_url', 'A.registry_id'))			
			->join(array('B' => 'registries'), 'A.registry_id = B.id', 
				array('B.consumer_key', 'B.consumer_secret', 'B.app_title', 
					'B.app_uri', 'B.app_desc'))
			->where('A.token = ?', $token)
			->where('A.token_type = ?', 'request')
			->where('A.token_ttl >= NOW()')
			->where('B.enabled = ?', $enabled);			
		
		// Dump SQL query for test purpose	
		//var_dump($select->__toString());
		
		$rs = $db->fetchRow($select);
		return $rs;
	}	
	
	/**
	 * Add request token
	 *
	 * @param string $consumerKey The API key
	 * @param array $options Optional token TTL
	 * @return array The information of token has been created
	 *
	 */
	public function addRequestToken($consumerKey, $options) {
		$db = Zend_Db_Table::getDefaultAdapter();		// data adapter
		
		$select = new Zend_Db_Select($db);		
		$select->from('registries', array('id', 'consumer_key', 'consumer_secret'))
			->where('consumer_key = ?', $consumerKey)
			->where('enabled = 1');		
		
		// Dump SQL query for test purpose	
		//var_dump($select->__toString());		
		
		$rs = $db->fetchRow($select);
		if(!is_array($rs)) {
			throw new OAuthException('No server with consumer_key "' . $consumerKey . '" or consumer_key is disabled');
		}
		
		if (isset($options['token_ttl']) && is_numeric($options['token_ttl'])) {
			$ttl = intval($options['token_ttl']);
		} else {
			$ttl = $this->_maxRequestTokenTtl;
		}
		
		if (!isset($options['oauth_callback'])) {
			// 1.0a Compatibility : store callback url associated with request token
			$options['oauth_callback'] = 'oob';
		}		
		
		$token = Utils::generateKey(true); // generate unique frob
		$tokenSecret = !isset($options['secret']) ? Utils::generateKey() : $options['secret'];
		
		$data = array(
			'registry_id' => $rs['id'],
			'user_id' => null,
			'authorized' => '0',
			'token' => $token,
			'token_secret' => $tokenSecret,
			'token_type' => 'request',
			'callback_url' => $options['oauth_callback'],
			'token_ttl' => new Zend_Db_Expr('DATE_ADD(NOW(), INTERVAL ' . $ttl . ' SECOND)')
			);
		// Insert token
		$db->insert('tokens', $data);
		
		return array('token' => $token, 'token_secret' => $tokenSecret, 
			'callback_url' => $options['oauth_callback'], 'token_ttl' => $ttl);
	}	
	
	/**
	 * Upgrade a request token to be an authorized request token.
	 * 
	 * @param string token The unauthorized request token
	 * @param int userId  user authorizing the token
	 * @param string referrerHost used to set the referrer host for 
	 * this token, for user feedback
	 */
	public function authorizeRequestToken($token, $userId, $referrer = '') {
		$db = Zend_Db_Table::getDefaultAdapter();
		
		// 1.0a Compatibility : create a token verifier
		$verifier = substr(md5(rand()), 0, 10);
				
		$data =  array(
			'authorized' => 1,
			'user_id' => $userId,
			'referrer_host' => $referrer,
			'verifier' => $verifier,
			);
		
		$where = array(); 
		$where[] = $db->quoteInto('token = ?', $token); 
		$where[] = $db->quoteInto('token_type = ?', 'request'); 
		
		$db->update('tokens', $data, $where);
		
		return $verifier;
	}
	
	/**
	 * This is method deleteRequestToken
	 *
	 * @param mixed $token This is a description
	 * @return void
	 *
	 */
	public function deleteRequestToken($token) {
		$db = Zend_Db_Table::getDefaultAdapter();
		$where = array();
		$where[] = 	$db->quoteInto('token = ?', $token);
		$where[] = 	$db->quoteInto('token_type = ?', 'request');
		
		$db->delete('tokens', $where);
	}	
	
	/**
	 * Exchange an authorized request token for new access token.
	 * 
	 * @param string token The authorized request token
	 * @param array options Options for the token, token_ttl
	 * @exception Exception when token could not be exchanged
	 * @return array (token, token_ttl)
	 */
	public function exchangeRequestForAccessToken($token, $options = array()) {		
		$db = Zend_Db_Table::getDefaultAdapter();		// data adapter
		
		$accessToken  = Utils::generateKey(true);
		$accessTokenSecret = Utils::generateKey();
		
		// Maximum time to live for this token
		if (isset($options['token_ttl']) && is_numeric($options['token_ttl'])) {
			$ttlExp = new Zend_Db_Expr('DATE_ADD(NOW(), INTERVAL ' . 
				intval($options['token_ttl']) . ' SECOND)');
		} else {		
			$ttlExp = new Zend_Db_Expr($db->quoteInto('DATE_ADD(?, INTERVAL 0 SECOND)', 
					$this->_maxTokenExpiredDate));
		}
		
		// Update token data
		$data =  array(
			'token' => $accessToken,
			'token_secret' => $accessTokenSecret,
			'token_type' => 'access',
			'token_ttl' => $ttlExp
			);		
		
		$where = array();
		$where[] = $db->quoteInto('token = ?', $token); 
		$where[] = $db->quoteInto('token_type = ?', 'request'); 
		$where[] = $db->quoteInto('authorized = ?', 1); 
		$where[] = $db->quoteInto('token_ttl >= ?', new Zend_Db_Expr('NOW()')); 
		
		if (isset($options['verifier'])) {
			$where[] = $db->quoteInto('verifier = ?', $options['verifier']);
		}		
		
		// Exchange token		
		$effected = $db->update('tokens', $data, $where);		
		if ($effected != 1) {
			throw new Exception('Can\'t exchange request token "' . 
				$token . '" for access token. No such token or not authorized');
		}
		
		// New access token
		$result = array('token' => $accessToken, 'token_secret' => $accessTokenSecret);		
		
		// Get TTL of new token
		$select = new Zend_Db_Select($db);		
		$ttlExp = new Zend_Db_Expr($db->quoteInto('IF(token_ttl >= ?, NULL, UNIX_TIMESTAMP(token_ttl) - UNIX_TIMESTAMP(NOW()))', 
				$this->_maxTokenExpiredDate));
		$select->from('tokens', array('token_ttl' => $ttlExp, 'user_id'))
			->where('token = ?', $accessToken);
		
		// Dump SQL query for test purpose	
		//var_dump($select->__toString());
		
		// Get TTL of access token
		$row = $db->fetchRow($select);		
		if (is_numeric($row['token_ttl'])) {
			$result['token_ttl'] = intval($row['token_ttl']);
		} else {
			$result['token_ttl'] = $this->_maxTokenExpiredDate;
		}
		
		$result['user_id'] = $row['user_id'];
		
		// return result
		return $result;
	}	
	
	/**
	 * Exchange an authorized request token for new access token.
	 * 
	 * @param string token The authorized request token
	 * @param array options Options for the token, token_ttl
	 * @exception Exception when token could not be exchanged
	 * @return array (token, token_ttl)
	 */
	public function createAccessToken($request, $options = array()) {		
		$db = Zend_Db_Table::getDefaultAdapter();		// data adapter
		
		$accessToken  = Utils::generateKey(true);
		$accessTokenSecret = Utils::generateKey();
		
		// Maximum time to live for this token
		if (isset($options['token_ttl']) && is_numeric($options['token_ttl'])) {
			$ttlExp = new Zend_Db_Expr('DATE_ADD(NOW(), INTERVAL ' . 
				intval($options['token_ttl']) . ' SECOND)');
		} else {		
			$ttlExp = new Zend_Db_Expr($db->quoteInto('DATE_ADD(?, INTERVAL 0 SECOND)', 
					$this->_maxTokenExpiredDate));
		}
		
		$data = array(
			'registry_id' => $request['registry_id'],
			'user_id' => $request['user_id'],
			'token' => $accessToken,
			'token_secret' => $accessTokenSecret,
			'token_type' => 'access',
			'authorized' => 1,
			'referrer_host' => 'client_auth',
			'token_ttl' => $ttlExp,	
			'verifier' => '',	
			'callback_url' => 'oob',	
			);	
		
		try {			
			$effected = $db->insert('tokens', $data);	// insert token
			if ($effected != 1) {
				throw new OAuthException('Can\'t create token information');
			}			
		} catch(Exception $e) {
			throw new OAuthException('Request rejected.');
		}
		
		// new access token
		$result = array('token' => $accessToken, 'token_secret' => $accessTokenSecret);		
		
		// Get TTL of new token
		$select = new Zend_Db_Select($db);		
		$ttlExp = new Zend_Db_Expr($db->quoteInto('IF(token_ttl >= ?, NULL, UNIX_TIMESTAMP(token_ttl) - UNIX_TIMESTAMP(NOW()))', 
				$this->_maxTokenExpiredDate));
		$select->from('tokens', array('token_ttl' => $ttlExp, 'user_id'))
			->where('token = ?', $accessToken);
		
		// dump SQL query for test purpose	
		//var_dump($select->__toString());
		
		// Get TTL of access token
		$row = $db->fetchRow($select);		
		if (is_numeric($row['token_ttl'])) {
			$result['token_ttl'] = intval($row['token_ttl']);
		} else {
			$result['token_ttl'] = $this->_maxTokenExpiredDate;
		}
		
		$result['user_id'] = $row['user_id'];
		
		// return result
		return $result;
	}		
	
	/**
	 * This is method getAccessToken
	 *
	 * @param mixed $token This is a description
	 * @param mixed $enabled This is a description
	 * @return mixed This is the return value description
	 *
	 */
	public function getAccessToken($token, $enabled = 1) {
		$db = Zend_Db_Table::getDefaultAdapter();
		$select = new Zend_Db_Select($db);	
		
		$select->from(array('A' => 'registries'), array('A.api_key', 'A.api_secret', 
			'A.app_title', 'A.app_desc', 'A.app_uri'))
			->join(array('B' => 'tokens'), 'A.id = B.registry_id', 
				array('B.user_id', 'B.authorized', 'B.registry_id', 
					'token_id' => 'B.id', 'B.token'))
			->where('B.token = ?', $token)
			->where('B.token_type = ?', 'access')
			->where('A.enabled = ?', $enabled)
			->where('B.token_ttl >= NOW()');
		
		$rs = $db->fetchRow($select);
		
		if (empty($rs)) {
			throw new Exception('The token "' . $token . '" does not exist or expired.');
		}
		
		return $rs;
	}		
	
	
	/**
	 * This is method deleteAccessToken
	 *
	 * @param mixed $token This is a description
	 * @return void
	 *
	 */
	public function deleteAccessToken($token, $userId, $isAdmin) {		
		$db = Zend_Db_Table::getDefaultAdapter();
		
		$where = array();
		$where[] = 	$db->quoteInto('token = ?', $token);
		$where[] = 	$db->quoteInto('token_type = ?', 'access');
		
		if ($isAdmin) {
			$where[] = 	$db->quoteInto('user_id = ?', $userId);
		}
		
		$db->delete('tokens', $where);
	}
	
	/**
	 * Set the ttl of a consumer access token.  This is done when the
	 * server receives a valid request with a xoauth_token_ttl parameter in it.
	 * 
	 * @param string token
	 * @param int ttl
	 */
	public function setAccessTokenTtl($token, $tokenTtl) {
		if ($tokenTtl <= 0) {
			// immediate delete when the token is past its ttl
			$this->deleteAccessToken($token, 0, true);
		} else {					
			// set maximum time to live for this token			
			$db = Zend_Db_Table::getDefaultAdapter();		// data adapter
			
			$ttlExp = new Zend_Db_Expr($db->quoteInto('DATE_ADD(?, INTERVAL 0 SECOND)', 
				$tokenTtl));
			
			// Update token data
			$data =  array(
				'token_ttl' => $ttlExp
				);		
			
			$where = array();
			$where[] = $db->quoteInto('token = ?', $token); 
			$where[] = $db->quoteInto('token_type = ?', 'access'); 
			
			// Update token		
			$effected = $db->update('tokens', $data, $where);
			return $effected;
		}
	}	
	
	/**
	 * This is method verifyTimestamp
	 *
	 * @param int $timestamp The timestamp to be checked
	 * @return void
	 * @exception Exception	thrown when the timestamp is expired
	 *
	 */
	public function verifyTimestamp($timestamp) {		
		$utc = gmdate("M d Y H:i:s", time());	// verify that timestamp is recentish		
		$now = strtotime($utc);
		if (abs($now - $timestamp) > $this->_timestampThreshold) {
			throw new OAuthException("Expired timestamp, yours $timestamp, ours $now");
		}
	}	
	
	/**
	 * Check an nonce/timestamp combination. Clears any nonce combinations
	 * that are older than the one received.
	 *
	 * @param string $key The API key
	 * @param string $token The token
	 * @param int $timestamp Timestamp
	 * @param string $nonce Nonce
	 * @return void
	 * @exception An exception is thrown when nonce is invalid
	 *
	 */
	public function verifyNonce($consumerKey, $token, $timestamp, $nonce) {		
		$db = Zend_Db_Table::getDefaultAdapter();		
		
		/* removed in Appendix A of RFC 5849
		// compare with MAX timestamp (last timestamp in sequence with same API key and token)		
		$select = new Zend_Db_Select($db);
		$select->from('nonces', 
			array('max_timestamp' => new Zend_Db_Expr('MAX(timestamp)'), 
					'exist' => new Zend_Db_Expr('MAX(timestamp) > ' . 
						($timestamp + $this->_timestampThreshold))))
			->where('consumer_key = ?', $consumerKey)
			->where('token = ?', $token);
		
		// Dump SQL query for test purpose	
		//var_dump($select->__toString());
		
		// Get TTL of access token
		$rs = $db->fetchRow($select);
		
		if (!empty($rs) && $rs['exist']) {
			throw new OAuthException('Timestamp is out of sequence. Request rejected. Got ' . 
				$timestamp . ' last max is ' . $rs['max_timestamp'] . ' allowed skew is ' . 
				$this->_timestampThreshold);
		}
		*/
		
		$data = array(
			'consumer_key' => $consumerKey,
			'token' => $token,
			'timestamp' => date('Y-m-d H:i:s', $timestamp),
			'nonce' => $nonce				
			);	
		
		try {			
			$effected = $db->insert('nonces', $data);	// insert token
			if ($effected != 1) {
				throw new OAuthException('Can\'t store nonce information');
			}			
		} catch(Exception $e) {
			throw new OAuthException('Duplicate timestamp/nonce combination, possible replay attack. Request rejected.');
		}
		
		$where = array();
		$where[] = 	$db->quoteInto('consumer_key = ?', $consumerKey);
		$where[] = 	$db->quoteInto('token = ?', $token);
		$where[] = 	$db->quoteInto('timestamp < ?', date('Y-m-d H:i:s', 
			$timestamp - $this->_timestampThreshold));
		
		$db->delete('nonces', $where);
	}
}

?>